package com.outlandr.irc.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

public class IRCServer extends Thread{
	
    private static Logger logger = Logger.getLogger("com.outlandr.irc");


	final private static String SERVER_NAME = "www.outlandr.net";
	
	private static int port = 6667;
	
	private static Map<String, User> users = new HashMap<String,User>();
	private static Map<String, Channel> channels = new HashMap<String, Channel>();
	private Socket socket;
	
	public IRCServer(Socket s) {
		this.socket = s;
	}
	
	@Override
	public void run() {
		// server loop

		try {
			PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
			
			InputStream is = socket.getInputStream();
			BufferedReader in = new BufferedReader(new InputStreamReader(is));

			String nick = "";
			String userInfo = "";
			
			String line = in.readLine();
			String[] temp = line.split(" ");
			
			// Get nick
			if (temp.length > 1 && "NICK".equalsIgnoreCase(temp[0])) {
				nick = temp[1];
			} else {
				// report error
			}
			// Get user
			User user = null;
			
			line = in.readLine();
			temp = line.split(" ");
			if (temp.length >= 5 && "USER".equalsIgnoreCase(temp[0])) {
//				for (int i = 1; i < temp.length; i++) {
//					userInfo += temp[i];
//				}
				userInfo = line;
			} else {
				// report error
			}
			

			if (!nick.isEmpty() && !userInfo.isEmpty()) {
				if (users.get(nick) != null) {
					// report error: nick name is already used
					String newNick = nick;
					int i = 1;
					while (users.get(newNick) != null) {
						newNick = nick + Integer.toString(i);
						i++;
					}
					nick = newNick;
				}
				user = new User(nick, userInfo, 
						socket.getInetAddress().getHostName(), out);
				synchronized (users) {
					users.put(nick, user);
				}
				
				DateFormat dateFormat = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss");
				Date date = new Date();
//				out.println(":www.outlandr.net 001 " +user.getNick()+ " :Welcome to www.outlandr.net " + user.getNick());
//				out.println(":www.outlandr.net 002 " +user.getNick()+ " :Your host is www.outlandr.net, running version 0.1");
//				out.println(":www.outlandr.net 003 " +user.getNick()+ " :This server was created " + dateFormat.format(date));
//				out.println(":www.outlandr.net 004 " +user.getNick()+ " www.outlandr.net 0.1 dioswkg biklmnopstv");
				
//				out.println(":www.outlandr.net 251 " +user.getNick()+ " :There are " + Integer.toString(users.size()) + " users and 0 invisible on 1 servers");
//				out.println(":www.outlandr.net 255 " +user.getNick()+ " :I have 1 clients and 0 servers");
//				out.println(":www.outlandr.net 422 " +user.getNick()+ " :MOTD File is missing");
				
//				out.println(":" + user.getNick() + " MODE " + user.getNick() + ":+iwx");
				
				String msg = String.format(":%s 001 %s :Wlecome to %s %s\n", SERVER_NAME, user.getNick(), SERVER_NAME, user.getNick());
				msg += String.format(":%s 002 %s :Your host is %s, running version 0.1\n", SERVER_NAME, user.getNick(), SERVER_NAME);
				msg += String.format(":%s 003 %s :This server was created %s\n", SERVER_NAME, user.getNick(), dateFormat.format(date));
				msg += String.format(":%s 004 %s %s 0.1 dioswkg biklmnopstv\n", SERVER_NAME, user.getNick(), SERVER_NAME);
				
				msg += String.format(":%s 251 %s :There are %s users and 0 invisible on 1 servers\n", SERVER_NAME, user.getNick(), Integer.toString(users.size()));
				msg += String.format(":%s 255 %s :I have 1 clients and 0 servers\n", SERVER_NAME, user.getNick());
				msg += String.format(":%s 422 %s :MOTD File is missing\n", SERVER_NAME, user.getNick());
				
				msg += String.format(":%s MODE %s:+iwx\n", user.getNick(), user.getNick());

//				out.println(":ar MODE ar :+iwx");
				
				out.println(msg);
			}
			for (;;) {
				line = in.readLine();
				System.out.println("Received: " + line);
				temp = line.split(" ");

				if (temp.length > 0) {
					
					// Handle Join
					if ("JOIN".equalsIgnoreCase(temp[0])) {
						// TODO handle multiple channels in the join command
						String channelName = temp[1];
						synchronized (channels) {
							Channel channel = channels.get(channelName);
							if (channel == null) {
								channel = new Channel(channelName);
								channels.put(channelName, channel);
							}
							channel.join(user);

							String msg;
							// send RPL_TOPIC
							// :ar1!n=ar1@pool-71-111-76-129.ptldor.dsl-w.verizon.net JOIN :##security
							msg = String.format(":%s!n=%s@%s JOIN :%s\n",
									user.getNick(), user.getUserName(), user.getHostName(), channelName);
							
							channel.sendMessage(msg);

							msg += String.format(":%s 332 %s %s :%s\n",
									"www.outlandr.com", user.getNick(), channelName, "topic");
//							msg.append(":www.outlandr.com 333 ar " + channel.getName() + " Kryczek 1242930603\n");
//							out.println(msg.toString());
							
							// send RPL_NAMREPLY
							// send list of users in this channel to the connecting user
							msg += String.format(":%s 353 %s = %s :" ,
									"www.outlandr.com", user.getNick(), channel.getName()); 
							User[] channelUsers = channel.getUsers();
							for (int i = channelUsers.length - 1 ; i > -1; i--) {
								User channelUser = channelUsers[i];
								msg += (channelUser.getNick());
								if (i > 0) {
									msg += ' ';
								}
							}
							msg += '\n';
							
//							 
//							out.println(msg.toString());
							
							
							msg += String.format(":%s 366 %s %s :End of /NAMES list", 
									"www.outlandr.com", user.getNick(), 
									channel.getName());
							
							System.out.println("DEBUG: sending " + msg);
							
							out.println(msg);
						}
						
					} else if ("PRIVMSG".equalsIgnoreCase(temp[0])) {
						// TODO recipient of PRVMSG may be a channel or a user or a list
						// 
						String channelName = temp[1];
						Channel channel = channels.get(channelName);
						Message message = new Message(user, line);
						channel.sendMessage(message);
						
					} else if ("LIST".equalsIgnoreCase(temp[0])) {
						
						String msg = String.format(":%s 321 %s Channel :Users Name\n",
								SERVER_NAME, user.getNick());
						
						for (Channel channel : channels.values()) {
							msg += String.format(":%s 322 %s %s %d :%s\n",
									SERVER_NAME, user.getNick(),
									channel.getName(), channel.getUsers().length,
									channel.getTopic());
							}
						msg += String.format(":%s 323 %s :End of /LIST\n",
								SERVER_NAME, user.getNick());
						System.out.println("Sending: \n" + msg);
						user.sendMessage(msg);
					} else if ("MODE".equalsIgnoreCase(temp[0])) {
						
						String channelName = temp[1];
						Channel channel = channels.get(channelName);
						String mode = channel.getMode();
						String msg = String.format(":%s 324 %s %s %s\n", 
								SERVER_NAME, user.getNick(), channelName, mode);
						msg += String.format(":%s 329 %s %s %d", 
								SERVER_NAME, user.getNick(), channelName, 1153342153);
						System.out.println("Sending: \n" + msg);
						user.sendMessage(msg);
						
					} else if("WHO".equalsIgnoreCase(temp[0])) {
						// :kubrick.freenode.net 315 ar1 ##security :End of /WHO list.
						String channelName = temp[1];
						String msg = String.format(":%s 315 %s %s :End of /WHO list.",
								SERVER_NAME, user.getNick(), channelName);
						
						user.sendMessage(msg);
						
					} else if ("PART".equalsIgnoreCase(temp[0])) {
						String channelName = temp[1];

						

						final Channel channel = channels.get(channelName);
						channel.leave(user);
						// :ar1!ar1@60E3C57F.30C48E1B.F21FEB01.IP PART #channel0 :Leaving
						String msg = String.format(":%s!%s@%s PART %s :Leaving", 
								user.getNick(), user.getNick(), user.getHostName(), channelName);
						
						channel.sendMessage(msg);
						
					} else if ("QUIT".equalsIgnoreCase(temp[0])) { // Handle quit
						for (Channel channel : channels.values()) {
							channel.leave(user);
						}
						users.remove(user.getNick());
						socket.close();
						break;
					}
				}
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
		super.run();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			ServerSocket ssocket = new ServerSocket(port);
			for (;;) {
				Socket s = ssocket.accept();
				Thread server = new IRCServer(s);
				server.start();
			}
			
		} catch (IOException e) {
			// log error
			e.printStackTrace();
		}

	}

}
